import java.awt.*;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;


public class TracerGrid extends AStarGrid {

	List<BoundingBox> boxlist = new ArrayList<BoundingBox>();
	List<Point> apath = null;
	List<Point> tracepath = new ArrayList<Point>();
	
	BoundingBox agent = new BoundingBox(0, 0, 10, 10);
	double agent_x, agent_y;
	
	public TracerGrid(int x, int y, int width, int height, int resolution) throws Exception {
		super(x, y, width, height, resolution);
	}
	
	public List<Point> tracePathAgent(Point end) {
		Point start = new Point(agent.x+agent.w/2, agent.y+agent.h/2);
		return tracePath(start, end);
	}
	
	public List<Point> tracePath(Point start, Point end) {
		// First clear the lists
		apath = null;
		open.clear();
		closed.clear();
		tracepath.clear();
		
		// Add the first tracepoint
		tracepath.add(start);
		
		// Check line of sight to end
		// If line of sight: jump over getpath and just add one node
		if(!raytestAllBoxes(start, end)) {
			tracepath.add(end);
			return tracepath;
		}
		
		Point mapped_start = translatePoint(start.x, start.y);
		Point mapped_end = translatePoint(end.x, end.y);
		if(!grid[mapped_start.x][mapped_start.y]) {
			apath = getPath(mapped_start,  mapped_end, false);
		}
		else if(!grid[mapped_end.x][mapped_end.y]) {
			apath = getPath(mapped_end, mapped_start, true);
		}
		
		if(apath == null || apath.isEmpty())
			return null;
		
		// Check line of sight to nearest node (start with the last node)
		// Check boxes + width and height of agent
		ListIterator<Point> it;
		
		Point a, b = start;
		Point center_end = apath.get(0);
		center_end = detranslatePoint(center_end.x, center_end.y);
		
		//int dehang = 0;
		boolean st = true;
		while( (b.x != center_end.x || b.y != center_end.y)/* && dehang < 100 && dehang > -100*/) {
			it = apath.listIterator();
			while(it.hasNext()) {
				a = it.next();
				a = detranslatePoint(a.x, a.y);
				//dehang--;
				if(!raytestAllBoxes(b, a)) {
					//dehang++;
					tracepath.add(a);
					b = a;
					st = false;
					break;
				}
			}
			if(st == true) {
				b = apath.get(apath.size()-1);
				b = detranslatePoint(b.x, b.y);
				tracepath.add(b);
				st = false;
			}
		}
		//System.out.println("" + dehang); // If dehang < -100 collides with box all the time
		
		// Uncomment if you wish to go to the original mouseposition
		// tracepath.add(end);
		
		return tracepath;
	}
	
	public boolean raytestAllBoxes(Point start, Point end) {
		
		ListIterator<BoundingBox> it = boxlist.listIterator();
		while(it.hasNext()) {
			BoundingBox a = it.next();
			if(raytestBoxAgent(start, end, a, agent))
				return true;
		}
		
		return false;
	}
	
	public boolean raytestBoxAgent(Point start, Point end, BoundingBox box, BoundingBox agentb) {
		int halfw = agentb.w / 2;
		int halfh = agentb.h / 2;
		BoundingBox newbox = new BoundingBox(box.x-halfw, box.y-halfh, box.w+agentb.w, box.h+agentb.h);
		return raytestBox(start, end, newbox);
	}
	
	public int pointRelLine(Point start, Point end, int x, int y) {
		return ((end.y-start.y)*x) + ((start.x-end.x)*y) + (end.x*start.y-start.x*end.y);
	}
	
	public boolean raytestBox(Point start, Point end, BoundingBox box) {
		// First check if all points of the rect are above or under the line
		int p1, p2, p3, p4;
		p1 = pointRelLine(start, end, box.x, box.y);
		p2 = pointRelLine(start, end, box.x+box.w, box.y);
		p3 = pointRelLine(start, end, box.x, box.y+box.h);
		p4 = pointRelLine(start, end, box.x+box.w, box.y+box.h);
		
		if(p1 > 0 && p2 > 0 && p3 > 0 && p4 > 0) return false; // If all points are on the right side
		if(p1 < 0 && p2 < 0 && p3 < 0 && p4 < 0) return false; // If all points are on the left side
		
		// Now check if line crosses the box
		if(start.x < box.x && end.x < box.x) return false;
		if(start.x > box.x+box.w && end.x > box.x+box.w) return false;
		if(start.y < box.y && end.y < box.y) return false;
		if(start.y > box.y+box.h && end.y > box.y+box.h) return false;
		
		return true;
	}
	
	public void setAgent(BoundingBox agentb) {
		agent = agentb;
	}
	
	public void centerposAgent(double x, double y) {
		agent_x = x;
		agent_y = y;
		agent = new BoundingBox(((int)x)-agent.w/2, ((int)y)-agent.h/2, agent.w, agent.h);
	}
	
	public void moveAgent(double speed) {
		
		// Check if moving is possible
		if(tracepath == null || tracepath.isEmpty() || tracepath.size() < 2)
			return;
		
		// Calculate normal
		Point start = tracepath.get(0);
		Point end = tracepath.get(1);
		
		if(start.x == end.x && start.y == end.y)
			return;
		
		double addx = end.x - start.x;
		double addy = end.y - start.y;
		
		double len = Math.sqrt( addx*addx + addy*addy );
		
		addx /= len;
		addy /= len;
		
		// Calculate new position
		double newx = agent_x + addx * speed;
		double newy = agent_y + addy * speed;
		centerposAgent(newx, newy);
	}
	
	public void drawAgent(Graphics2D g2d) {
		g2d.setColor(Color.green);
		g2d.drawRect(agent.x, agent.y, agent.w, agent.h);
	}
	
	public void addBox(BoundingBox box) {
		boxlist.add(box);
	}
	
	public void mapBox() {
		clearGrid();

		ListIterator<BoundingBox> it = boxlist.listIterator();
		while(it.hasNext()) {
			BoundingBox a = it.next();
			mapRect(a.x, a.y, a.w, a.h);
		}
	}
	
	public void drawBox(Graphics2D g2d) {
		g2d.setColor(Color.black);
		ListIterator<BoundingBox> it = boxlist.listIterator();
		while(it.hasNext()) {
			it.next().draw(g2d);
		}
	}

	public void drawPath(Graphics2D g2d) {
		if(apath == null)
			return;
		
		ListIterator<Point> it = apath.listIterator();
		while(it.hasNext()) {
			Point a = it.next();
			g2d.setColor(new Color(100, 200, 100));
			fillTile(g2d, a.x, a.y);
		}
	}
	
	public void drawTrace(Graphics2D g2d) {
		if(tracepath == null)
			return;
		
		Point a, b;
		
		ListIterator<Point> it = tracepath.listIterator();
		if(!it.hasNext())
			return;
			
		a = it.next();
		while(it.hasNext()) {
			b = it.next();
			g2d.setColor(Color.black);
			g2d.drawLine(a.x, a.y, b.x, b.y);
			a = b;
		}
	}
	
}
